﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BL.data;
using DAL;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using BL.objects;

namespace BL
{
    public class Manager
    {
        private Graph m_Graph = null;

        public List<Node> FindPathFromTo(int from, int to)
        {
            if (m_Graph == null)
            {
                GraphDataSet ds = new GraphDataSet(db.ExecuteSP("sp_Edges_select"));

                m_Graph = ds.ToGraph();
            }

            return m_Graph.FindShortestPath(from, to);
        }

        public List<InterestPoint> FindPointByName(string name)
        {
            SqlParameter pointName = new SqlParameter("pointName", name);

            //casting the result dataSet into interestPoint
            InterestPointDataSet ds = new InterestPointDataSet(db.ExecuteSP("sp_InterestPoints_select", pointName));

            return ds.ToIntrestPoints();
        }

        public Vertex FindVertexByLongLat(double longtitude, double latitude)
        {
            SqlParameter longPoint = new SqlParameter("pointLong", longtitude);
            SqlParameter latPoint = new SqlParameter("pointLat", latitude);

            //casting the result dataSet into VertexDataSet
            VertexDataSet ds = new VertexDataSet(db.ExecuteSP("sp_InterestPoints_select", longPoint, latPoint));

            //return the first (and only) vertex that was found
            return ds.ToVertices()[1];
        }

        public int AddVertex(double longtitude, double latitude)
        {
            SqlParameter longtitudePoint = new SqlParameter("pointLong", longtitude);
            SqlParameter latitudePoint = new SqlParameter("pointLat", latitude);

            //casting the result dataSet into interestPoint
            //adding the long,lat into the db
            //checking the action

            ResultsDataSet ds = new ResultsDataSet(db.ExecuteSP("sp_Vertices_insert", longtitudePoint, latitudePoint));

            return ds.ActionStatus();
        }

        public int AddIntrestPoint(int id, string pointName, string text)
        {
            SqlParameter vertex_id = new SqlParameter("vertexId", id);
            SqlParameter point_Name = new SqlParameter("pointName", pointName);
            SqlParameter point_Desc = new SqlParameter("description", text);

            ResultsDataSet ds = new ResultsDataSet(db.ExecuteSP("sp_InterestPoints_insert", vertex_id, point_Name, point_Desc));

            return ds.ActionStatus();
        }

        public void UpdateIntrestPoint(int id, String pointName, String text)
        {
            SqlParameter vertex_id = new SqlParameter("vertexId", id);
            SqlParameter point_Name = new SqlParameter("pointName", pointName);
            SqlParameter point_Desc = new SqlParameter("description", text);

            ResultsDataSet ds = new ResultsDataSet(db.ExecuteSP("sp_InterestPoints_update", vertex_id, point_Name, point_Desc));
        }

        //done in app.svc
        public int AddEdge(int inVertex, int outVertex, double weight)
        {
            SqlParameter inVertexPoint = new SqlParameter("in_vertex", inVertex);
            SqlParameter outVertexPoint = new SqlParameter("out_vertex", outVertex);
            SqlParameter edgeWeight = new SqlParameter("weight", weight);

            //adding the edge
            ResultsDataSet ds = new ResultsDataSet(db.ExecuteSP("sp_Edges_insert", inVertexPoint, outVertexPoint, edgeWeight));

            return ds.ActionStatus();
        }


        public void AddVertexAndCreateEdge(double longtitude, double latitude, int in_vertex_id, List<LongLat> coordinates,int weight)
        {
            //add given location as a new Vertex into the database
            int out_vertex_id = AddVertex(longtitude, latitude);

            /*
             * add a new Edge with:
             * 1. first vertex - the "in" vertex
             * 2. last vertex - the "out" vertex
             * 3. weight - given, or will be calculated
             * **/
            int edge_id = AddEdge(in_vertex_id, out_vertex_id, weight);

            //insert the coordinates list into the database
            AddEdgesCoordinates(edge_id , coordinates);
        }

        public void AddEdgesCoordinates(int edge_id, List<LongLat> coordinates)
        {
            SqlParameter edgeId = new SqlParameter("@edgeId", edge_id);
            for (int i = 0; i < coordinates.Count  ; i++)
            {
                SqlParameter latitude = new SqlParameter("@pointLat", coordinates[i].Latitude);
                SqlParameter longtitude = new SqlParameter("@pointLat", coordinates[i].Latitude);


                ResultsDataSet ds = new ResultsDataSet(db.ExecuteSP("sp_Edge_Coordinates_insert", edgeId, latitude, longtitude));

            }
        }

        public List<LongLat> SelectCoordinatesByEdge(int edge_id)
        {
            SqlParameter edgeId = new SqlParameter("@edgeId", edge_id);

            LongLatDataSet ds = new LongLatDataSet(db.ExecuteSP("sp_Edge_Coordinates_select", edgeId));

            return ds.ToLongLat();
        }
        

        public double CalculateDistanceBetweenTwoVertices(Vertex inVertex, Vertex outVertex)
        {
            double R = 6371; // km
            double dLat = DegreeToRadian((outVertex.LongLat.Latitude - inVertex.LongLat.Latitude));
            double dLon = DegreeToRadian((outVertex.LongLat.Longtitude - inVertex.LongLat.Longtitude));
            double lat1 = DegreeToRadian(inVertex.LongLat.Latitude);
            double lat2 = DegreeToRadian(outVertex.LongLat.Latitude);

            double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                    Math.Sin(dLon / 2) * Math.Sin(dLon / 2) * Math.Cos(lat1) * Math.Cos(lat2);
            double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
            return R * c;
        }

        public List<Vertex> CheckExistingVertexInRange(double longtitude, double latitude)
        {
            SqlParameter pointLong = new SqlParameter("pointLong", longtitude);
            SqlParameter pointLat = new SqlParameter("pointLat", latitude);
            SqlParameter radius = new SqlParameter("radius", ConfigurationManager.AppSettings["radius"]);

            //casting the result dataSet into interestPoint
            VertexDataSet ds = new VertexDataSet(db.ExecuteSP("sp_SelectVertexInRange", pointLong, pointLat, radius));

            return ds.ToVertices();
        }

        private double DegreeToRadian(double angle)
        {
            return Math.PI * angle / 180.0;
        }

        public void GeneratePaths()
        {
            VertexDataSet ds = new VertexDataSet(db.ExecuteSP("sp_Vertices_select"));

            List<Vertex> vertices = ds.ToVertices();

            foreach (Vertex vertex in vertices)
            {
                foreach (Vertex vertex2 in vertices)
                {
                    if (vertex != vertex2)
                    {
                        List<Node> pathNodes = FindPathFromTo(vertex.Id, vertex2.Id);

                        int[] nodesIds = (from node in pathNodes
                                             select node.Id).ToArray();

                        string path = string.Join(",", nodesIds);

                        
                        SqlParameter sqlVertexA = new SqlParameter("verA", vertex.Id);
                        SqlParameter sqlVertexB = new SqlParameter("verB", vertex2.Id);
                        SqlParameter sqlPath = new SqlParameter("path", path);

                        db.ExecuteSP("sp_Paths_insert", sqlVertexA, sqlVertexB, sqlPath);
                    }
                }
            }
        }
    }
}
